// ==========================================================
// Apple Macintosh QuickDraw/PICT Loader
//
// Design and implementation by
// - Amir Ebrahimi (amir@unity3d.com)
//
// Based on PICT loading code from paintlib (http://www.paintlib.de/paintlib/).
//
// Paintlib License:
// The paintlib source code and all documentation are copyright (c) 1996-2002
// Ulrich von Zadow and other contributors.
//
// The paintlib source code is supplied "AS IS". Ulrich von Zadow and other
// contributors disclaim all warranties, expressed or implied, including, without
// limitation, the warranties of merchantability and of fitness for any purpose.
// The authors assume no liability for direct, indirect, incidental, special,
// exemplary, or consequential damages, which may result from the use of paintlib,
// even if advised of the possibility of such damage.
//
// Permission is hereby granted to use, copy, modify, and distribute this source
// code, or portions hereof, for any purpose, without fee, subject to the following
// restrictions:
//
// 1. The origin of this source code must not be misrepresented.
// 2. Altered versions must be plainly marked as such and must not be misrepresented
//    as being the original source.
// 3. This Copyright notice may not be removed or altered from any source or altered
//    source distribution.
// 4. Executables containing paintlib or parts of it must state that the software
//    "contains paintlib code. paintlib is copyright (c) 1996-2002 Ulrich von Zadow
//    and other contributors.". This notice must be displayed in at least one place
//    where the copyright for the software itself is displayed. The documentation must
//    also contain this notice.
//
// Bug fixes were made to the original code to support version 2 PICT files
// properly.
//
// Additional resources:
// http://developer.apple.com/documentation/mac/QuickDraw/QuickDraw-458.html
// http://www.fileformat.info/format/macpict/egff.htm
//
// Notes (http://lists.apple.com/archives/java-dev/2006/Apr/msg00588.html):
// There are three main types of PICT files:
//  - Version 1
//  - Version 2
//  - Extended Version 2
//
// Some things to look out for:
//  - The bounds and target DPI are stored in a different place in all three.
//  - Some of the values are fixed-point shorts ( short / 65536f )
//  - Values are big endian
//  - All of this may be *preceded* by a 512 byte header--sometimes it is
//    there, and sometimes it isn't. You just have to check for the magic
//    values in both places.
//
// This file is part of FreeImage 3
//
// COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
// THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE
// OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED
// CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT
// THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY
// SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL
// PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER
// THIS DISCLAIMER.
//
// Use at your own risk!
// ==========================================================

#include "FreeImage.h"
#include "Utilities.h"

// ==========================================================
// Plugin Interface
// ==========================================================
static int s_format_id;

static const int outputMessageSize = 256;

// ==========================================================
// Internal functions
// ==========================================================

static BYTE
Read8(FreeImageIO *io, fi_handle handle) {
    BYTE i = 0;
    io->read_proc(&i, 1, 1, handle);
    return i;
}

static WORD
Read16(FreeImageIO *io, fi_handle handle) {
    // reads a two-byte big-endian integer from the given file and returns its value.
    // assumes unsigned.

    unsigned hi = Read8(io, handle);
    unsigned lo = Read8(io, handle);
    return (WORD)(lo + (hi << 8));
}

static unsigned
Read32(FreeImageIO *io, fi_handle handle) {
    // reads a four-byte big-endian integer from the given file and returns its value.
    // assumes unsigned.

    unsigned b3 = Read8(io, handle);
    unsigned b2 = Read8(io, handle);
    unsigned b1 = Read8(io, handle);
    unsigned b0 = Read8(io, handle);
    return (b3 << 24) + (b2 << 16) + (b1 << 8) + b0;
}

// ----------------------------------------------------------

struct OpDef
{
    const char * name;
    int    len;
    const char * description;
};

// for reserved opcodes
#define res(length) { "reserved", (length), "reserved for Apple use" }
#define RGB_LEN 6
#define WORD_LEN -1
#define NA 0

static OpDef optable[] =
{
    /* 0x00 */  { "NOP",               0, "nop" },
    /* 0x01 */  { "Clip",             NA, "clip" },
    /* 0x02 */  { "BkPat",             8, "background pattern" },
    /* 0x03 */  { "TxFont",            2, "text font (word)" },
    /* 0x04 */  { "TxFace",            1, "text face (byte)" },
    /* 0x05 */  { "TxMode",            2, "text mode (word)" },
    /* 0x06 */  { "SpExtra",           4, "space extra (fixed point)" },
    /* 0x07 */  { "PnSize",            4, "pen size (point)" },
    /* 0x08 */  { "PnMode",            2, "pen mode (word)" },
    /* 0x09 */  { "PnPat",             8, "pen pattern" },
    /* 0x0a */  { "FillPat",           8, "fill pattern" },
    /* 0x0b */  { "OvSize",            4, "oval size (point)" },
    /* 0x0c */  { "Origin",            4, "dh, dv (word)" },
    /* 0x0d */  { "TxSize",            2, "text size (word)" },
    /* 0x0e */  { "FgColor",           4, "foreground color (longword)" },
    /* 0x0f */  { "BkColor",           4, "background color (longword)" },
    /* 0x10 */  { "TxRatio",           8, "numerator (point), denominator (point)" },
    /* 0x11 */  { "Version",           1, "version (byte)" },
    /* 0x12 */  { "BkPixPat",         NA, "color background pattern" },
    /* 0x13 */  { "PnPixPat",         NA, "color pen pattern" },
    /* 0x14 */  { "FillPixPat",       NA, "color fill pattern" },
    /* 0x15 */  { "PnLocHFrac",        2, "fractional pen position" },
    /* 0x16 */  { "ChExtra",           2, "extra for each character" },
    /* 0x17 */  res(0),
    /* 0x18 */  res(0),
    /* 0x19 */  res(0),
    /* 0x1a */  { "RGBFgCol",    RGB_LEN, "RGB foreColor" },
    /* 0x1b */  { "RGBBkCol",    RGB_LEN, "RGB backColor" },
    /* 0x1c */  { "HiliteMode",        0, "hilite mode flag" },
    /* 0x1d */  { "HiliteColor", RGB_LEN, "RGB hilite color" },
    /* 0x1e */  { "DefHilite",         0, "Use default hilite color" },
    /* 0x1f */  { "OpColor",           6, "RGB OpColor for arithmetic modes" },
    /* 0x20 */  { "Line",              8, "pnLoc (point), newPt (point)" },
    /* 0x21 */  { "LineFrom",          4, "newPt (point)" },
    /* 0x22 */  { "ShortLine",         6, "pnLoc (point, dh, dv (-128 .. 127))" },
    /* 0x23 */  { "ShortLineFrom",     2, "dh, dv (-128 .. 127)" },
    /* 0x24 */  res(WORD_LEN),
    /* 0x25 */  res(WORD_LEN),
    /* 0x26 */  res(WORD_LEN),
    /* 0x27 */  res(WORD_LEN),
    /* 0x28 */  { "LongText",         NA, "txLoc (point), count (0..255), text" },
    /* 0x29 */  { "DHText",           NA, "dh (0..255), count (0..255), text" },
    /* 0x2a */  { "DVText",           NA, "dv (0..255), count (0..255), text" },
    /* 0x2b */  { "DHDVText",         NA, "dh, dv (0..255), count (0..255), text" },
    /* 0x2c */  res(WORD_LEN),
    /* 0x2d */  res(WORD_LEN),
    /* 0x2e */  res(WORD_LEN),
    /* 0x2f */  res(WORD_LEN),
    /* 0x30 */  { "frameRect",         8, "rect" },
    /* 0x31 */  { "paintRect",         8, "rect" },
    /* 0x32 */  { "eraseRect",         8, "rect" },
    /* 0x33 */  { "invertRect",        8, "rect" },
    /* 0x34 */  { "fillRect",          8, "rect" },
    /* 0x35 */  res(8),
    /* 0x36 */  res(8),
    /* 0x37 */  res(8),
    /* 0x38 */  { "frameSameRect",     0, "rect" },
    /* 0x39 */  { "paintSameRect",     0, "rect" },
    /* 0x3a */  { "eraseSameRect",     0, "rect" },
    /* 0x3b */  { "invertSameRect",    0, "rect" },
    /* 0x3c */  { "fillSameRect",      0, "rect" },
    /* 0x3d */  res(0),
    /* 0x3e */  res(0),
    /* 0x3f */  res(0),
    /* 0x40 */  { "frameRRect",        8, "rect" },
    /* 0x41 */  { "paintRRect",        8, "rect" },
    /* 0x42 */  { "eraseRRect",        8, "rect" },
    /* 0x43 */  { "invertRRect",       8, "rect" },
    /* 0x44 */  { "fillRRrect",        8, "rect" },
    /* 0x45 */  res(8),
    /* 0x46 */  res(8),
    /* 0x47 */  res(8),
    /* 0x48 */  { "frameSameRRect",    0, "rect" },
    /* 0x49 */  { "paintSameRRect",    0, "rect" },
    /* 0x4a */  { "eraseSameRRect",    0, "rect" },
    /* 0x4b */  { "invertSameRRect",   0, "rect" },
    /* 0x4c */  { "fillSameRRect",     0, "rect" },
    /* 0x4d */  res(0),
    /* 0x4e */  res(0),
    /* 0x4f */  res(0),
    /* 0x50 */  { "frameOval",         8, "rect" },
    /* 0x51 */  { "paintOval",         8, "rect" },
    /* 0x52 */  { "eraseOval",         8, "rect" },
    /* 0x53 */  { "invertOval",        8, "rect" },
    /* 0x54 */  { "fillOval",          8, "rect" },
    /* 0x55 */  res(8),
    /* 0x56 */  res(8),
    /* 0x57 */  res(8),
    /* 0x58 */  { "frameSameOval",     0, "rect" },
    /* 0x59 */  { "paintSameOval",     0, "rect" },
    /* 0x5a */  { "eraseSameOval",     0, "rect" },
    /* 0x5b */  { "invertSameOval",    0, "rect" },
    /* 0x5c */  { "fillSameOval",      0, "rect" },
    /* 0x5d */  res(0),
    /* 0x5e */  res(0),
    /* 0x5f */  res(0),
    /* 0x60 */  { "frameArc",         12, "rect, startAngle, arcAngle" },
    /* 0x61 */  { "paintArc",         12, "rect, startAngle, arcAngle" },
    /* 0x62 */  { "eraseArc",         12, "rect, startAngle, arcAngle" },
    /* 0x63 */  { "invertArc",        12, "rect, startAngle, arcAngle" },
    /* 0x64 */  { "fillArc",          12, "rect, startAngle, arcAngle" },
    /* 0x65 */  res(12),
    /* 0x66 */  res(12),
    /* 0x67 */  res(12),
    /* 0x68 */  { "frameSameArc",      4, "rect, startAngle, arcAngle" },
    /* 0x69 */  { "paintSameArc",      4, "rect, startAngle, arcAngle" },
    /* 0x6a */  { "eraseSameArc",      4, "rect, startAngle, arcAngle" },
    /* 0x6b */  { "invertSameArc",     4, "rect, startAngle, arcAngle" },
    /* 0x6c */  { "fillSameArc",       4, "rect, startAngle, arcAngle" },
    /* 0x6d */  res(4),
    /* 0x6e */  res(4),
    /* 0x6f */  res(4),
    /* 0x70 */  { "framePoly",        NA, "poly" },
    /* 0x71 */  { "paintPoly",        NA, "poly" },
    /* 0x72 */  { "erasePoly",        NA, "poly" },
    /* 0x73 */  { "invertPoly",       NA, "poly" },
    /* 0x74 */  { "fillPoly",         NA, "poly" },
    /* 0x75 */  res(NA),
    /* 0x76 */  res(NA),
    /* 0x77 */  res(NA),
    /* 0x78 */  { "frameSamePoly",     0, "poly (NYI)" },
    /* 0x79 */  { "paintSamePoly",     0, "poly (NYI)" },
    /* 0x7a */  { "eraseSamePoly",     0, "poly (NYI)" },
    /* 0x7b */  { "invertSamePoly",    0, "poly (NYI)" },
    /* 0x7c */  { "fillSamePoly",      0, "poly (NYI)" },
    /* 0x7d */  res(0),
    /* 0x7e */  res(0),
    /* 0x7f */  res(0),
    /* 0x80 */  { "frameRgn",         NA, "region" },
    /* 0x81 */  { "paintRgn",         NA, "region" },
    /* 0x82 */  { "eraseRgn",         NA, "region" },
    /* 0x83 */  { "invertRgn",        NA, "region" },
    /* 0x84 */  { "fillRgn",          NA, "region" },
    /* 0x85 */  res(NA),
    /* 0x86 */  res(NA),
    /* 0x87 */  res(NA),
    /* 0x88 */  { "frameSameRgn",      0, "region (NYI)" },
    /* 0x89 */  { "paintSameRgn",      0, "region (NYI)" },
    /* 0x8a */  { "eraseSameRgn",      0, "region (NYI)" },
    /* 0x8b */  { "invertSameRgn",     0, "region (NYI)" },
    /* 0x8c */  { "fillSameRgn",       0, "region (NYI)" },
    /* 0x8d */  res(0),
    /* 0x8e */  res(0),
    /* 0x8f */  res(0),
    /* 0x90 */  { "BitsRect",         NA, "copybits, rect clipped" },
    /* 0x91 */  { "BitsRgn",          NA, "copybits, rgn clipped" },
    /* 0x92 */  res(WORD_LEN),
    /* 0x93 */  res(WORD_LEN),
    /* 0x94 */  res(WORD_LEN),
    /* 0x95 */  res(WORD_LEN),
    /* 0x96 */  res(WORD_LEN),
    /* 0x97 */  res(WORD_LEN),
    /* 0x98 */  { "PackBitsRect",     NA, "packed copybits, rect clipped" },
    /* 0x99 */  { "PackBitsRgn",      NA, "packed copybits, rgn clipped" },
    /* 0x9a */  { "Opcode_9A",        NA, "the mysterious opcode 9A" },
    /* 0x9b */  res(WORD_LEN),
    /* 0x9c */  res(WORD_LEN),
    /* 0x9d */  res(WORD_LEN),
    /* 0x9e */  res(WORD_LEN),
    /* 0x9f */  res(WORD_LEN),
    /* 0xa0 */  { "ShortComment",      2, "kind (word)" },
    /* 0xa1 */  { "LongComment",      NA, "kind (word), size (word), data" }
};

// ----------------------------------------------------------

struct MacRect
{
    WORD top;
    WORD left;
    WORD bottom;
    WORD right;
};

struct MacpixMap
{
    // Ptr baseAddr              // Not used in file.
    // short rowBytes            // read in seperatly.
    struct MacRect Bounds;
    WORD version;
    WORD packType;
    LONG packSize;
    LONG hRes;
    LONG vRes;
    WORD pixelType;
    WORD pixelSize;
    WORD cmpCount;
    WORD cmpSize;
    LONG planeBytes;
    LONG pmTable;
    LONG pmReserved;
};

struct MacRGBColour
{
    WORD red;
    WORD green;
    WORD blue;
};

struct MacPoint
{
    WORD x;
    WORD y;
};

struct MacPattern // Klaube
{
    BYTE pix[64];
};

// ----------------------------------------------------------

static void
ReadRect( FreeImageIO *io, fi_handle handle, MacRect* rect ) {
    rect->top = Read16( io, handle );
    rect->left = Read16( io, handle );
    rect->bottom = Read16( io, handle );
    rect->right = Read16( io, handle );
}

static void
ReadPixmap( FreeImageIO *io, fi_handle handle, MacpixMap* pPixMap ) {
    pPixMap->version = Read16( io, handle );
    pPixMap->packType = Read16( io, handle );
    pPixMap->packSize = Read32( io, handle );
    pPixMap->hRes = Read16( io, handle );
    Read16( io, handle );
    pPixMap->vRes = Read16( io, handle );
    Read16( io, handle );
    pPixMap->pixelType = Read16( io, handle );
    pPixMap->pixelSize = Read16( io, handle );
    pPixMap->cmpCount = Read16( io, handle );
    pPixMap->cmpSize = Read16( io, handle );
    pPixMap->planeBytes = Read32( io, handle );
    pPixMap->pmTable = Read32( io, handle );
    pPixMap->pmReserved = Read32( io, handle );
}

/**
   Reads a mac color table into a bitmap palette.
*/
static void
ReadColorTable( FreeImageIO *io, fi_handle handle, WORD* pNumColors, RGBQUAD* pPal ) {
    LONG        ctSeed;
    WORD        ctFlags;
    WORD        val;
    int         i;

    ctSeed = Read32( io, handle );
    ctFlags = Read16( io, handle );
    WORD numColors = Read16( io, handle )+1;
    *pNumColors = numColors;

    for (i = 0; i < numColors; i++) {
        val = Read16( io, handle );
        if (ctFlags & 0x8000) {
            // The indicies in a device colour table are bogus and
            // usually == 0, so I assume we allocate up the list of
            // colours in order.
            val = (WORD)i;
        }
        if (val >= numColors) {
            throw "pixel value greater than color table size.";
        }
        // Mac colour tables contain 16-bit values for R, G, and B...
        pPal[val].rgbRed = ((BYTE) (((WORD) (Read16( io, handle )) >> 8) & 0xFF));
        pPal[val].rgbGreen = ((BYTE) (((WORD) (Read16( io, handle )) >> 8) & 0xFF));
        pPal[val].rgbBlue = ((BYTE) (((WORD) (Read16( io, handle )) >> 8) & 0xFF));
    }
}

/**
   skips unneeded packbits.
   pixelSize == Source bits per pixel.
*/
static void
SkipBits( FreeImageIO *io, fi_handle handle, MacRect* bounds, WORD rowBytes, int pixelSize ) {
    int    i;
    WORD   pixwidth;           // bytes per row when uncompressed.

    int height = bounds->bottom - bounds->top;
    int width = bounds->right - bounds->left;

    // High bit of rowBytes is flag.
    if (pixelSize <= 8) {
        rowBytes &= 0x7fff;
    }
    pixwidth = (WORD)width;

    if (pixelSize == 16) {
        pixwidth *= 2;
    }
    if (rowBytes == 0) {
        rowBytes = pixwidth;
    }
    if (rowBytes < 8) {
        io->seek_proc( handle, rowBytes*height, SEEK_CUR );
    }
    else {
        for (i = 0; i < height; i++) {
            int lineLen;            // length of source line in bytes.
            if (rowBytes > 250) {
                lineLen = Read16( io, handle );
            } else {
                lineLen = Read8( io, handle );
            }
            io->seek_proc( handle, lineLen, SEEK_CUR );
        }
    }
}

/**
   Skip polygon or region
*/
static void
SkipPolyOrRegion( FreeImageIO *io, fi_handle handle ) {
    WORD len = Read16( io, handle ) - 2;
    io->seek_proc(handle, len, SEEK_CUR);
}

/**
   Width in bytes for 8 bpp or less.
   Width in pixels for 16 bpp.
   Expands Width units to 32-bit pixel data.
*/
static void
expandBuf( FreeImageIO *io, fi_handle handle, int width, int bpp, BYTE* dst ) {
    switch (bpp) {
        case 16:
            for ( int i=0; i<width; i++) {
                WORD src = Read16( io, handle );
                dst[ FI_RGBA_BLUE ] = (src & 31)*8;             // Blue
                dst[ FI_RGBA_GREEN ] = ((src >> 5) & 31)*8;     // Green
                dst[ FI_RGBA_RED ] = ((src >> 10) & 31)*8;      // Red
                dst[ FI_RGBA_ALPHA ] = 0xFF;                    // Alpha
                dst += 4;
            }
            break;
        default:
            throw "Bad bits per pixel in expandBuf.";
    }
}

/**
   Expands Width units to 8-bit pixel data.
   Max. 8 bpp source format.
*/
static void
expandBuf8( FreeImageIO *io, fi_handle handle, int width, int bpp, BYTE* dst )
{
    switch (bpp) {
        case 8:
            io->read_proc( dst, width, 1, handle );
            break;
        case 4:
            for (int i = 0; i < width; i++) {
                WORD src = Read8( io, handle );
                *dst = (src >> 4) & 15;
                *(dst+1) = (src & 15);
                dst += 2;
            }
            if (width & 1) { // Odd Width?
                WORD src = Read8( io, handle );
                *dst = (src >> 4) & 15;
                dst++;
            }
            break;
        case 2:
            for (int i = 0; i < width; i++) {
                WORD src = Read8( io, handle );
                *dst = (src >> 6) & 3;
                *(dst+1) = (src >> 4) & 3;
                *(dst+2) = (src >> 2) & 3;
                *(dst+3) = (src & 3);
                dst += 4;
            }
            if (width & 3)  { // Check for leftover pixels
                for (int i = 6; i > 8 - (width & 3) * 2; i -= 2) {
                    WORD src = Read8( io, handle );
                    *dst = (src >> i) & 3;
                    dst++;
                }
            }
            break;
        case 1:
            for (int i = 0; i < width; i++) {
                WORD src = Read8( io, handle );
                *dst = (src >> 7) & 1;
                *(dst+1) = (src >> 6) & 1;
                *(dst+2) = (src >> 5) & 1;
                *(dst+3) = (src >> 4) & 1;
                *(dst+4) = (src >> 3) & 1;
                *(dst+5) = (src >> 2) & 1;
                *(dst+6) = (src >> 1) & 1;
                *(dst+7) = (src  & 1);
                dst += 8;
            }
            if (width & 7) {  // Check for leftover pixels
                for (int i = 7; i > (8-width & 7); i--) {
                    WORD src = Read8( io, handle );
                    *dst = (src >> i) & 1;
                    dst++;
                }
            }
            break;
        default:
            throw "Bad bits per pixel in expandBuf8.";
    }
}

static BYTE*
UnpackPictRow( FreeImageIO *io, fi_handle handle, BYTE* pLineBuf, int width, int rowBytes, int srcBytes ) {

    if (rowBytes < 8) { // Ah-ha!  The bits aren't actually packed.  This will be easy.
        io->read_proc( pLineBuf, rowBytes, 1, handle );
    }
    else {
        BYTE* pCurPixel = pLineBuf;

        // Unpack RLE. The data is packed bytewise.
        for (int j = 0; j < srcBytes; ) {
            BYTE FlagCounter = Read8( io, handle );
            if (FlagCounter & 0x80) {
                if (FlagCounter == 0x80) {
                    // Special case: repeat value of 0.
                    // Apple says ignore.
                    j++;
                } else {
                    // Packed data.
                    int len = ((FlagCounter ^ 255) & 255) + 2;
                    BYTE p = Read8( io, handle );
                    memset( pCurPixel, p, len);
                    pCurPixel += len;
                    j += 2;
                }
            }
            else {
                // Unpacked data
                int len = (FlagCounter & 255) + 1;
                io->read_proc( pCurPixel, len, 1, handle );
                pCurPixel += len;
                j += len + 1;
            }
        }
    }

    return pLineBuf;
}

/**
   This routine decompresses BitsRects with a packType of 4 (and 32 bits per pixel).
   In this format, each line is separated into 8-bit-bitplanes and then compressed via RLE.
   To decode, the routine decompresses each line & then juggles the bytes around to get pixel-oriented data.
   NumBitPlanes == 3 if RGB, 4 if RGBA
*/
static void
Unpack32Bits( FreeImageIO *io, fi_handle handle, FIBITMAP* dib, MacRect* bounds, WORD rowBytes, int numPlanes ) {
    int height = bounds->bottom - bounds->top;
    int width = bounds->right - bounds->left;

    if (rowBytes == 0) {
        rowBytes = (WORD)( width * 4 );
    }

    BYTE* pLineBuf = (BYTE*)malloc( rowBytes ); // Let's allocate enough for 4 bit planes
    if ( pLineBuf ) {
        try {
            for ( int i = 0; i < height; i++ ) {
                // for each line do...
                int linelen;            // length of source line in bytes.
                if (rowBytes > 250) {
                    linelen = Read16( io, handle );
                } else {
                    linelen = Read8( io, handle);
                }

                BYTE* pBuf = UnpackPictRow( io, handle, pLineBuf, width, rowBytes, linelen );

                // Convert plane-oriented data into pixel-oriented data &
                // copy into destination bitmap.
                BYTE* dst = (BYTE*)FreeImage_GetScanLine( dib, height - 1 - i);

                if ( numPlanes == 3 ) {
                    for ( int j = 0; j < width; j++ ) {
                        // For each pixel in line...
                        dst[ FI_RGBA_BLUE ] = (*(pBuf+width*2));     // Blue
                        dst[ FI_RGBA_GREEN ] = (*(pBuf+width));       // Green
                        dst[ FI_RGBA_RED ] = (*pBuf);             // Red
                        dst[ FI_RGBA_ALPHA ] = (0xFF);
                        dst += 4;
                        pBuf++;
                    }
                } else {
                    for ( int j = 0; j < width; j++ ) {
                        // For each pixel in line...
                        dst[ FI_RGBA_BLUE ] = (*(pBuf+width*3));     // Blue
                        dst[ FI_RGBA_GREEN ] = (*(pBuf+width*2));     // Green
                        dst[ FI_RGBA_RED ] = (*(pBuf+width));       // Red
                        dst[ FI_RGBA_ALPHA ] = (*pBuf);
                        dst += 4;
                        pBuf++;
                    }
                }
            }
        }
        catch( ... ) {
            free( pLineBuf );
            throw;
        }
    }
    free( pLineBuf );
}

/**
   Decompression routine for 8 bpp.
   rowBytes is the number of bytes each source row would take if it were uncompressed.
   This _isn't_ equal to the number of pixels in the row - it seems apple pads the data to a word boundary and then compresses it.
   Of course, we have to decompress the excess data and then throw it away.
*/
static void
Unpack8Bits( FreeImageIO *io, fi_handle handle, FIBITMAP* dib, MacRect* bounds, WORD rowBytes ) {
    int height = bounds->bottom - bounds->top;
    int width = bounds->right - bounds->left;

    // High bit of rowBytes is flag.
    rowBytes &= 0x7fff;

    if (rowBytes == 0) {
        rowBytes = (WORD)width;
    }

    for ( int i = 0; i < height; i++ ) {
        int linelen;            // length of source line in bytes.
        if (rowBytes > 250) {
            linelen = Read16( io, handle );
        } else {
            linelen = Read8( io, handle );
        }
        BYTE* dst = (BYTE*)FreeImage_GetScanLine( dib, height - 1 - i);
        dst = UnpackPictRow( io, handle, dst, width, rowBytes, linelen );
    }
}

/**
   Decompression routine for everything but 8 & 32 bpp.
   This routine is slower than the two routines above since it has to deal with a lot of special cases :-(.
   It's also a bit chaotic because of these special cases...
   unpack8bits is basically a dumber version of unpackbits.
   pixelSize == Source bits per pixel.
*/
static void
UnpackBits( FreeImageIO *io, fi_handle handle, FIBITMAP* dib, MacRect* bounds, WORD rowBytes, int pixelSize ) {
    WORD   pixwidth;           // bytes per row when uncompressed.
    int    pkpixsize;
    int    PixelPerRLEUnit;

    char outputMessage[ outputMessageSize ] = "";

    int height = bounds->bottom - bounds->top;
    int width = bounds->right - bounds->left;

    // High bit of rowBytes is flag.
    if (pixelSize <= 8) {
        rowBytes &= 0x7fff;
    }

    pixwidth = (WORD)width;
    pkpixsize = 1;          // RLE unit: one byte for everything...
    if (pixelSize == 16) {    // ...except 16 bpp.
        pkpixsize = 2;
        pixwidth *= 2;
    }

    if (rowBytes == 0) {
        rowBytes = pixwidth;
    }

    {
        // I allocate the temporary line buffer here. I allocate too
        // much memory to compensate for sloppy (& hence fast) decompression.
        switch (pixelSize) {
            case 1:
                PixelPerRLEUnit = 8;
                break;
            case 2:
                PixelPerRLEUnit = 4;
                break;
            case 4:
                PixelPerRLEUnit = 2;
                break;
            case 8:
                PixelPerRLEUnit = 1;
                break;
            case 16:
                PixelPerRLEUnit = 1;
                break;
            default:
                sprintf( outputMessage, "Illegal bpp value in unpackbits: %d\n", pixelSize );
                throw outputMessage;
        }

        if (rowBytes < 8) {
            // ah-ha!  The bits aren't actually packed.  This will be easy.
            for ( int i = 0; i < height; i++ ) {
                BYTE* dst = (BYTE*)FreeImage_GetScanLine( dib, height - 1 - i);
                if (pixelSize == 16) {
                    expandBuf( io, handle, width, pixelSize, dst );
                } else {
                    expandBuf8( io, handle, width, pixelSize, dst );
                }
            }
        }
        else {
            for ( int i = 0; i < height; i++ ) {
                // For each line do...
                int    linelen;            // length of source line in bytes.
                if (rowBytes > 250) {
                    linelen = Read16( io, handle );
                } else {
                    linelen = Read8( io, handle );
                }

                BYTE* dst = (BYTE*)FreeImage_GetScanLine( dib, height - 1 - i);
                BYTE FlagCounter;

                // Unpack RLE. The data is packed bytewise - except for
                // 16 bpp data, which is packed per pixel :-(.
                for ( int j = 0; j < linelen; ) {
                    FlagCounter = Read8( io, handle );
                    if (FlagCounter & 0x80) {
                        if (FlagCounter == 0x80) {
                            // Special case: repeat value of 0.
                            // Apple says ignore.
                            j++;
                        }
                        else {
                            // Packed data.
                            int len = ((FlagCounter ^ 255) & 255) + 2;

                            // This is slow for some formats...
                            if (pixelSize == 16) {
                                expandBuf( io, handle, 1, pixelSize, dst );
                                for ( int k = 1; k < len; k++ ) {
                                    // Repeat the pixel len times.
                                    memcpy( dst+(k*4*PixelPerRLEUnit), dst, 4*PixelPerRLEUnit);
                                }
                                dst += len*4*PixelPerRLEUnit;
                            }
                            else {
                                expandBuf8( io, handle, 1, pixelSize, dst );
                                for ( int k = 1; k < len; k++ ) {
                                    // Repeat the expanded byte len times.
                                    memcpy( dst+(k*PixelPerRLEUnit), dst, PixelPerRLEUnit);
                                }
                                dst += len*PixelPerRLEUnit;
                            }
                            j += pkpixsize + 1;
                        }
                    }
                    else {
                        // Unpacked data
                        int len = (FlagCounter & 255) + 1;
                        if (pixelSize == 16) {
                            expandBuf( io, handle, len, pixelSize, dst );
                            dst += len*4*PixelPerRLEUnit;
                        }
                        else {
                            expandBuf8( io, handle, len, pixelSize, dst );
                            dst += len*PixelPerRLEUnit;
                        }
                        j += ( len * pkpixsize ) + 1;
                    }
                }
            }
        }
    }
}

static void
DecodeOp9a( FreeImageIO *io, fi_handle handle, FIBITMAP* dib, MacpixMap* pixMap ) {
    // Do the actual unpacking.
    switch ( pixMap->pixelSize ) {
        case 32:
            Unpack32Bits( io, handle, dib, &pixMap->Bounds, 0, pixMap->cmpCount );
            break;
        case 8:
            Unpack8Bits( io, handle, dib, &pixMap->Bounds, 0 );
            break;
        default:
            UnpackBits( io, handle, dib, &pixMap->Bounds, 0, pixMap->pixelSize );
    }
}

static void
DecodeBitmap( FreeImageIO *io, fi_handle handle, FIBITMAP* dib, BOOL isRegion, MacRect* bounds, WORD rowBytes ) {
    WORD mode = Read16( io, handle );

    if ( isRegion ) {
        SkipPolyOrRegion( io, handle );
    }

    RGBQUAD* pal = FreeImage_GetPalette( dib );
    if ( !pal ) {
        throw "No palette for bitmap!";
    }

    for (int i = 0; i < 2; i++) {
        unsigned char val = i ? 0xFF : 0x0;
        pal[i].rgbRed = val;
        pal[i].rgbGreen = val;
        pal[i].rgbBlue = val;
    }

    UnpackBits( io, handle, dib, bounds, rowBytes, 1 );
}

static void
DecodePixmap( FreeImageIO *io, fi_handle handle, FIBITMAP* dib, BOOL isRegion, MacpixMap* pixMap, WORD rowBytes ) {
    // Read mac colour table into windows palette.
    WORD numColors;    // Palette size.
    RGBQUAD ct[256];

    ReadColorTable( io, handle, &numColors, ct );
    if ( FreeImage_GetBPP( dib ) == 8 ) {
        RGBQUAD* pal = FreeImage_GetPalette( dib );
        if ( !pal ) {
            throw "No palette for bitmap!";
        }

        for (int i = 0; i < numColors; i++) {
            pal[i].rgbRed = ct[ i ].rgbRed;
            pal[i].rgbGreen = ct[ i ].rgbGreen;
            pal[i].rgbBlue = ct[ i ].rgbBlue;
        }
    }

    // Ignore source & destination rectangle as well as transfer mode.
    MacRect tempRect;
    ReadRect( io, handle, &tempRect );
    ReadRect( io, handle, &tempRect );
    WORD mode = Read16( io, handle );

    if ( isRegion) {
        SkipPolyOrRegion( io, handle );
    }

    switch ( pixMap->pixelSize ) {
        case 32:
            Unpack32Bits( io, handle, dib, &pixMap->Bounds, rowBytes, pixMap->cmpCount );
            break;
        case 8:
            Unpack8Bits( io, handle, dib, &pixMap->Bounds, rowBytes );
            break;
        default:
            UnpackBits( io, handle, dib, &pixMap->Bounds, rowBytes, pixMap->pixelSize );
    }
}

// ==========================================================
// Plugin Implementation
// ==========================================================

static const char * DLL_CALLCONV
Format() {
    return "PICT";
}

static const char * DLL_CALLCONV
Description() {
    return "Macintosh PICT";
}

static const char * DLL_CALLCONV
Extension() {
    return "pct,pict,pic";
}

static const char * DLL_CALLCONV
MimeType() {
    return "image/x-pict";
}

static BOOL DLL_CALLCONV
Validate(FreeImageIO *io, fi_handle handle) {
    if(io->seek_proc(handle, 522, SEEK_SET) == 0) {
        BYTE pict_signature[] = { 0x00, 0x11, 0x02, 0xFF, 0x0C, 0X00 };
        BYTE signature[6];

        if(io->read_proc(signature, 1, sizeof(pict_signature), handle)) {
            // v1.0 files have 0x11 (version operator) followed by 0x01 (version number)
            // v2.0 files have 0x0011 (version operator) followed by 0x02ff (version number)
            //   and additionally 0x0c00 as a header opcode
            // Currently, we are only supporting v2.0
            return (memcmp(pict_signature, signature, sizeof(pict_signature)) == 0);
        } else {
            return FALSE;
        }
    }

    return FALSE;
}

static BOOL DLL_CALLCONV
SupportsExportDepth(int depth) {
    return FALSE;
}

static BOOL DLL_CALLCONV
SupportsExportType(FREE_IMAGE_TYPE type) {
    return FALSE;
}

static BOOL DLL_CALLCONV
SupportsICCProfiles() {
    return FALSE;
}

/**
   This plugin decodes macintosh PICT files with 1,2,4,8,16 and 32 bits per pixel as well as PICT/JPEG.
   If an alpha channel is present in a 32-bit-PICT, it is decoded as well.
   The PICT format is a general picture file format and can contain a lot of other elements besides bitmaps.
   These elements are ignored.
*/
static FIBITMAP * DLL_CALLCONV
Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) {
    char outputMessage[ outputMessageSize ] = "";
    FIBITMAP* dib = NULL;
    try {
        // Skip empty 512 byte header.
        if ( !io->seek_proc(handle, 512, SEEK_CUR) == 0 )
            return NULL;

        // Read PICT header
        Read16( io, handle ); // Skip version 1 picture size

        MacRect frame;
        ReadRect( io, handle, &frame );

        BYTE b = 0;
        while ((b = Read8(io, handle)) == 0);
        if ( b != 0x11 ) {
            throw "invalid header: version number missing.";
        }

        int version = Read8( io, handle );
        if ( version == 2 && Read8( io, handle ) != 0xff ) {
            throw "invalid header: illegal version number.";
        }

        enum PICTType {none, op9a, jpeg, pixmap, bitmap};
        PICTType pictType = none;

        MacRect bounds;
        MacpixMap pixMap;
        int hRes = 0x480000; // in pixels/inch (72 by default == 0x480000 in fixed point)
        int vRes = 0x480000; // in pixels/inch (72 by default == 0x480000 in fixed point)
        WORD rowBytes = 0;
        BOOL isRegion = FALSE;
        BOOL done = FALSE;
        long currentPos = 0;

        while ( !done ) {
            WORD opcode = 0;

            // get the current stream position (used to avoid infinite loops)
            currentPos = io->tell_proc(handle);

            if ((version == 1) || ((io->tell_proc( handle ) % 2) != 0)) {
                // align to word for version 2
                opcode = Read8( io, handle );
            }
            if (version == 2) {
                opcode = Read16( io, handle );
            }

            if (opcode == 0xFF || opcode == 0xFFFF) {
                done = TRUE;
                throw "PICT contained only vector data!";
            }
            else if (opcode < 0xa2) {
                switch (opcode) {
                    case 0x01:
                        {
                            // skip clipping rectangle
                            MacRect clipRect;
                            WORD len = Read16( io, handle );

                            if (len == 0x000a) {
                                /* null rgn */
                                ReadRect( io, handle, &clipRect );
                            } else {
                                io->seek_proc(handle, len - 2, SEEK_CUR);
                            }
                            break;
                        }
                    case 0x12:
                    case 0x13:
                    case 0x14:
                        {
                            // skip pattern definition
                            WORD       patType;
                            WORD       rowBytes;
                            MacpixMap  p;
                            WORD       numColors;

                            patType = Read16( io, handle );

                            switch( patType ) {
                                case 2:
                                    io->seek_proc(handle, 8, SEEK_CUR);
                                    io->seek_proc(handle, 5, SEEK_CUR);
                                    break;
                                case 1:
                                    {
                                        io->seek_proc(handle, 8, SEEK_CUR);
                                        rowBytes = Read16( io, handle );
                                        ReadRect( io, handle, &p.Bounds );
                                        ReadPixmap( io, handle, &p);

                                        RGBQUAD ct[256];
                                        ReadColorTable(io, handle, &numColors, ct );
                                        SkipBits( io, handle, &p.Bounds, rowBytes, p.pixelSize );
                                        break;
                                    }
                                default:
                                    throw "Unknown pattern type.";
                            }

                            break;
                        }
                    case 0x70:
                    case 0x71:
                    case 0x72:
                    case 0x73:
                    case 0x74:
                    case 0x75:
                    case 0x76:
                    case 0x77:
                        {
                            SkipPolyOrRegion( io, handle );
                            break;
                        }
                    case 0x90:
                    case 0x98:
                        {
                            // Bitmap/pixmap data clipped by a rectangle.
                            rowBytes = Read16( io, handle );    // Bytes per row in source when uncompressed.
                            isRegion = FALSE;

                            if ( rowBytes & 0x8000) {
                                pictType = pixmap;
                            } else {
                                pictType = bitmap;
                            }
                            done = TRUE;
                            break;
                        }
                    case 0x91:
                    case 0x99:
                        {
                            // Bitmap/pixmap data clipped by a region.
                            rowBytes = Read16( io, handle );    // Bytes per row in source when uncompressed.
                            isRegion = TRUE;

                            if ( rowBytes & 0x8000) {
                                pictType = pixmap;
                            } else {
                                pictType = bitmap;
                            }
                            done = TRUE;
                            break;
                        }
                    case 0x9a:
                        {
                            // DirectBitsRect.
                            Read32( io, handle );           // Skip fake len and fake EOF.
                            Read16( io, handle );           // bogus row bytes.

                            // Read in the PixMap fields.
                            ReadRect( io, handle, &pixMap.Bounds );
                            ReadPixmap( io, handle, &pixMap );

                            // Ignore source & destination rectangle as well as transfer mode.
                            MacRect dummy;
                            ReadRect( io, handle, &dummy );
                            ReadRect( io, handle, &dummy );
                            WORD mode = Read16( io, handle );

                            pictType=op9a;
                            done = TRUE;
                            break;
                        }
                    case 0xa1:
                        {
                            // long comment
                            WORD type;
                            WORD len;

                            type = Read16( io, handle );
                            len = Read16( io, handle);
                            if (len > 0) {
                                io->seek_proc(handle, len, SEEK_CUR);
                            }
                            break;
                        }
                    default:
                        // No function => skip to next opcode
                        if (optable[opcode].len == WORD_LEN) {
                            WORD len = Read16( io, handle );
                            io->seek_proc(handle, len, SEEK_CUR);
                        } else {
                            io->seek_proc(handle, optable[opcode].len, SEEK_CUR);
                        }
                        break;
                }
            }
            else if (opcode == 0xc00) {
                // version 2 header (26 bytes)
                WORD minorVersion = Read16( io, handle );   // always FFFE (-2) for extended version 2
                Read16( io, handle );                       // reserved
                hRes = Read32( io, handle );                // original horizontal resolution in pixels/inch
                vRes = Read32( io, handle );                // original horizontal resolution in pixels/inch
                MacRect dummy;
                ReadRect( io, handle, &dummy );             // frame bounds at original resolution
                Read32( io, handle );                       // reserved
            }
            else if (opcode == 0x8200) {
                // jpeg
                long opLen = Read32( io, handle );
                BOOL found = FALSE;
                int i = 0;

                // skip to JPEG header.
                while ( !found && i < opLen ) {
                    //                  io->seek_proc( handle, 24, SEEK_CUR );
                    //                  MacRect dummy;
                    //                  ReadRect( io, handle, &dummy );
                    //                  io->seek_proc( handle, 122, SEEK_CUR );
                    //                  found = TRUE;
                    BYTE data[ 2 ];
                    if( io->read_proc( data, 2, 1, handle ) ) {
                        io->seek_proc( handle, -2, SEEK_CUR );

                        if ( data[0] == 0xFF && data[1] == 0xD8 ) {
                            found = TRUE;
                        } else {
                            Read8( io, handle );
                            i++;
                        }
                    }
                }

                if ( found ) {
                    // Pass the data to the JPEG decoder.
                    pictType = jpeg;
                } else {
                    throw "PICT file contains unrecognized quicktime data.";
                }
                done = TRUE;
            }
            else if (opcode >= 0xa2 && opcode <= 0xaf) {
                // reserved
                WORD len = Read16( io, handle );
                io->seek_proc(handle, len, SEEK_CUR);
            }
            else if ((opcode >= 0xb0 && opcode <= 0xcf) || (opcode >= 0x8000 && opcode <= 0x80ff)) {
                // just a reserved opcode, no data
            }
            else if ((opcode >= 0xd0 && opcode <= 0xfe) || opcode >= 8100) {
                // reserved
                LONG len = Read32( io, handle );
                io->seek_proc(handle, len, SEEK_CUR);
            }
            else if (opcode >= 0x100 && opcode <= 0x7fff) {
                // reserved
                io->seek_proc(handle, ((opcode >> 7) & 255), SEEK_CUR);
            }
            else {
                sprintf( outputMessage, "Can't handle opcode %x.\n", opcode );
                throw outputMessage;
            }

            if(currentPos == io->tell_proc(handle)) {
                // we probaly reached the end of file as we can no longer move forward ...
                throw "Invalid PICT file";
            }
        }

        switch ( pictType ) {
            case op9a:
                {
                    bounds = pixMap.Bounds;
                    int width = bounds.right - bounds.left;
                    int height = bounds.bottom - bounds.top;

                    if ( pixMap.pixelSize > 8 ) {
                        dib = FreeImage_Allocate( width, height, 32, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK);
                    } else {
                        dib = FreeImage_Allocate( width, height, 8);
                    }
                    hRes = pixMap.hRes << 16;
                    vRes = pixMap.vRes << 16;
                    break;
                }

            case jpeg:
                {
                    dib = FreeImage_LoadFromHandle( FIF_JPEG, io, handle );
                    break;
                }

            case pixmap:
                {
                    // Decode version 2 pixmap
                    ReadRect( io, handle, &pixMap.Bounds );
                    ReadPixmap( io, handle, &pixMap );

                    bounds = pixMap.Bounds;
                    int width = bounds.right - bounds.left;
                    int height = bounds.bottom - bounds.top;

                    if ( pixMap.pixelSize > 8 ) {
                        dib = FreeImage_Allocate( width, height, 32, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK);
                    } else {
                        dib = FreeImage_Allocate( width, height, 8);
                    }
                    hRes = pixMap.hRes << 16;
                    vRes = pixMap.vRes << 16;
                    break;
                }

            case bitmap:
                {
                    // Decode version 1 bitmap: 1 bpp.
                    MacRect srcRect;
                    MacRect dstRect;
                    WORD width;        // Width in pixels
                    WORD height;       // Height in pixels

                    ReadRect( io, handle, &bounds );
                    ReadRect( io, handle, &srcRect );
                    ReadRect( io, handle, &dstRect );

                    width = bounds.right - bounds.left;
                    height = bounds.bottom - bounds.top;

                    dib = FreeImage_Allocate(width, height, 8);
                    break;
                }
        }

        if ( dib ) {
            // need to convert resolution figures from fixed point, pixels/inch
            // to floating point, pixels/meter.
            float hres_ppm = hRes * ((float)39.4 / (float)65536.0);
            float vres_ppm = vRes * ((float)39.4 / (float)65536.0);

            FreeImage_SetDotsPerMeterX( dib, (LONG)hres_ppm );
            FreeImage_SetDotsPerMeterY( dib, (LONG)vres_ppm );

            switch( pictType ) {
                case op9a:
                    DecodeOp9a( io, handle, dib, &pixMap );
                    break;
                case jpeg:
                    // Already decoded if the embedded format was valid.
                    break;
                case pixmap:
                    DecodePixmap( io, handle, dib, isRegion, &pixMap, rowBytes );
                    break;
                case bitmap:
                    DecodeBitmap( io, handle, dib, isRegion, &bounds, rowBytes );
                    break;
                default:
                    throw "invalid pict type";
            }
        }

        return dib;
    }
    catch(const char *message) {
        FreeImage_Unload( dib );
        FreeImage_OutputMessageProc(s_format_id, message);
    }

    return NULL;
}

// ==========================================================
//   Init
// ==========================================================

void DLL_CALLCONV
InitPICT(Plugin *plugin, int format_id) {
    s_format_id = format_id;

    plugin->format_proc = Format;
    plugin->description_proc = Description;
    plugin->extension_proc = Extension;
    plugin->regexpr_proc = NULL;
    plugin->open_proc = NULL;
    plugin->close_proc = NULL;
    plugin->pagecount_proc = NULL;
    plugin->pagecapability_proc = NULL;
    plugin->load_proc = Load;
    plugin->save_proc = NULL;
    plugin->validate_proc = Validate;
    plugin->mime_proc = MimeType;
    plugin->supports_export_bpp_proc = SupportsExportDepth;
    plugin->supports_export_type_proc = SupportsExportType;
    plugin->supports_icc_profiles_proc = SupportsICCProfiles;
}
